/**
 * 
 */
package idv.takeshi.software.productline.bookstore.application.service.spring;

import idv.takeshi.software.productline.bookstore.application.service.MaintainOrdersService;
import idv.takeshi.software.productline.bookstore.application.service.dto.PaymentDto;
import idv.takeshi.software.productline.bookstore.domain.model.order.Order;
import idv.takeshi.software.productline.bookstore.domain.model.order.OrderItem;
import idv.takeshi.software.productline.bookstore.domain.model.order.OrderRepository;
import idv.takeshi.software.productline.bookstore.domain.model.order.OrderStatus;
import idv.takeshi.software.productline.bookstore.domain.model.order.OrderStatusToUnderProcessingException;
import idv.takeshi.software.productline.bookstore.domain.model.order.PickingList;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccount;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccountRepository;

import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Set;

import org.apache.commons.lang.Validate;
import org.apache.commons.lang.reflect.MethodUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * A service implementation for MaintainOrdersService interface.
 * @author takeshi
 *
 */
@Service("MaintainOrdersService")
@Transactional
public class MaintainOrdersServiceImpl implements MaintainOrdersService {
	
	private static final Log log = LogFactory.getLog(MaintainOrdersServiceImpl.class);
	
	@Autowired
	private UserAccountRepository userAccountRepository; 
	
	@Autowired
	private OrderRepository orderRepository;
	
	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.application.service.MaintainOrdersService#getOrders(java.lang.String, java.lang.String, idv.takeshi.software.productline.bookstore.domain.model.order.OrderStatus, java.util.Date, java.util.Date, java.util.Date, java.util.Date, java.math.BigDecimal, java.math.BigDecimal)
	 */
	public Set<Order> getOrders(String userName, String recipientName,
			OrderStatus orderStatus, Date fromUpdateTime, Date toUpdateTime,
			Date fromCreateTime, Date toCreateTime, BigDecimal fromPrice,
			BigDecimal toPrice) {
		return this.orderRepository.find(userName, recipientName, orderStatus, 
				fromUpdateTime, toUpdateTime, fromCreateTime, toCreateTime, fromPrice, toPrice);
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.application.service.MaintainOrdersService#getOrderById(java.lang.Long)
	 */
	public Order getOrderById(Long orderId) {
		return this.orderRepository.findById(orderId);
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.application.service.MaintainOrdersService#getUserAccountByOrderId(java.lang.Long)
	 */
	public UserAccount getUserAccountByOrderId(Long orderId) {
		return this.userAccountRepository.findByOrderId(orderId);
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.application.service.MaintainOrdersService#updatePayment(idv.takeshi.software.productline.bookstore.application.service.dto.PaymentDto)
	 */
	public void updatePayment(PaymentDto paymentDto) {
		Validate.notNull(paymentDto, "paymentDto shall always not be null");
		Long orderId = paymentDto.getOrderId();
		Validate.notNull(orderId, "orderId shall always not be null");
		
		Order order = this.orderRepository.findById(orderId);
		paymentDto.loadModel(order);
		
		Date dateTime = new Date();
		order.setUpdateTime(dateTime);
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.application.service.MaintainOrdersService#cancelOrder(java.lang.Long)
	 */
	public void cancelOrder(Long orderId) {
		Validate.notNull(orderId, "orderId shall always not be null");
		Order order = this.orderRepository.findById(orderId);
		Validate.notNull(order, "order shall always not be null");
		order.cancel();
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.application.service.MaintainOrdersService#goToNextStatus(java.lang.Long, java.lang.String)
	 */
	public void goToNextStatus(Long orderId, String methodName) throws OrderStatusToUnderProcessingException {
		Validate.notNull(orderId, "orderId shall always not be null");
		Validate.notEmpty(methodName, "methodName shall always not be null/empty");
		
		Order order = this.orderRepository.findById(orderId);
		Validate.notNull(order, "order shall always not be null");
		
		try {
			MethodUtils.invokeExactMethod(order, methodName, null);
			
		} catch(InvocationTargetException e){ 
			if(e.getTargetException() instanceof OrderStatusToUnderProcessingException){
				OrderStatusToUnderProcessingException e1 = 
					(OrderStatusToUnderProcessingException) e.getTargetException(); 
				log.error("failed to move order:" + orderId + "'s status to " + OrderStatus.UNDER_PROCESSING, 
						e1);
				throw e1;
			} else {
				generateRuntimeException(orderId, methodName, e);
			}
		} catch (Exception e) {
			generateRuntimeException(orderId, methodName, e);
		}
	}

	/**
	 * @param orderId
	 * @param methodName
	 * @param e
	 */
	private void generateRuntimeException(Long orderId, String methodName,
			Exception e) {
		String msg = "reflective executing method:" + methodName + " failed for order:" + orderId;
		log.error(msg, e);
		throw new RuntimeException(msg, e);
	}

}
